/*
 * File:   Block.cpp
 * Author: Usama, Max, Andy
 *
 * Created on February 4, 2011, 10:17 AM
 */

#include "Block.h"
#include "BlockException.h"


#include <stdio.h>
#include <unistd.h>
#include <iostream>

/**
 * Creates a RAM representation of a block of data. Doesn't do any error
 * checking. Initializes the buffer of data (data member, m_buffer) to zeros.
 * Does not alter the disk.
 * @param blockNumber should range betwen 1 and (1 less than the number of
 * blocks on the Disk). This is used to set a data member for the Block.
 * @param length should be the same as the block size on the Disk.
 * Allocates a new RAM byte array m_buffer.
 */
Block::Block(int blockNumber, int length){
    m_blockNumber = blockNumber;
// Is it neccessary to check if block number is within range?

// initializing to buffer
    m_buffer = new unsigned char[length];
    int nullIndex;
    for(int i = 0; i < length; i++){
        nullIndex = i;
        m_buffer[i] = NULL;
    }
    m_buffer[nullIndex] = NULL;
}
/**
 *Creates a Block object in RAM. Initializes the m_buffer of data to zeros.
 * The block number data member is set to 0. Does not alter the disk.
 * @param length is the number of bytes to allocate for the m_buffer.
 */
Block::Block(int length){
    m_buffer = new unsigned char[length];
    int nullIndex;
    for(int i = 0; i < length; i++){
        nullIndex = i;
        m_buffer[i] = NULL;
    }
    m_buffer[nullIndex] = NULL;
}
/**
 *Attempts to read a Block from a Disk. If the block as written to Disk
 * had a next or second pointer value, that next or second pointer value
 * will be available after use of this constructor by calling GetPointer().
 * @param blockNumber The block number of the Block to read.
 * This can range between 0 and GetNumberOfBlocks()-1 (inclusive)
 * for this Disk object.
 * @param disk is the Disk to read from.
 */
Block::Block(int blockNumber, Disk* disk){
    m_blockNumber = blockNumber;
    m_buffer = new unsigned char[Disk::DEFAULT_BLOCK_SIZE];
    if (blockNumber < 0 || blockNumber > (disk->numberOfBlocks() - 1))
        throw "ERROR - Cannot Read the Block Number";

    if (m_buffer == NULL)
        throw "ERROR - Block is Empty";
    bool success = disk->ReadBlock(m_blockNumber, m_buffer);
    if(!success) throw "ERROR - Block construction failed";
}
/**
 *Deallocates the m_buffer for the Block.
 */
Block::~Block(){
    delete [] m_buffer;
}

/**
 *Changes the reference of the m_buffer. No copy is made!
 * Memory for the original m_buffer is first deallocated by
 * this method (using "delete").
 * @param number is used by reference-- a copy is not made.
 * Do not deallocate the buffer passed to this call after the call.
 */
void Block::SetBlockNumber(int number){
    m_blockNumber = number;
}

/**
 * Returns the block number for this Block, returns value of a data member.
 * Does not access Disk.
 * @return the block number
 */
int Block::GetBlockNumber(){
    return m_blockNumber;
}

/**
 * Changes the reference of the m_buffer. No copy is made!
 * Memory for the original m_buffer is first deallocated by
 * this method (using "delete").
 * @param buffer is used by reference-- a copy is not made.
 * Do not deallocate the buffer passed to this call after the call.
 */
void Block::SetBuffer(unsigned char* buffer){
    delete [] m_buffer;
    //printf("Delete buffer success");
    m_buffer = NULL;
    m_buffer = buffer;
}
/**
 * Obtain a reference to the m_buffer data member for this Block.
 * @return a reference to (not a copy of) the byte buffer for this Block.
 */
unsigned char* Block::GetBuffer(){
    return m_buffer;
}

/**
 * Set all bytes of the m_buffer of this Block to 0. Does not alter the disk.
 */
void Block::ClearBuffer(){
    int nullIndex;
    for(int i = 0; i < Disk::DEFAULT_BLOCK_SIZE; i++){
        nullIndex = i;
        m_buffer[i] = NULL;
    }
    m_buffer[nullIndex] = NULL;
}

/**
 * Writes the data (m_buffer) from the Block to its block number on the Disk.
 * The Block must have the correct information. E.g., this can be the case
 * because a previous Block constructor reading from a Disk returned the Block.
 * @param disk The Disk to write this Block to.
 * @return true iff the call succeeded in writing the Block to the Disk.
 */
bool Block::Write(Disk* disk){
    if (disk->WriteBlock(m_blockNumber, m_buffer) != true){
        std::cout<< "ERROR - Cannot Write Block Back to Disk" <<std::endl;
        return false;
    }
    return true;
}
/**
 *Changes one of the pointers in the m_buffer. If location=0, the
 * first 4 bytes of the m_buffer are changed to the binary value in the
 * pointer. ("first pointer") If location=1, the second 4 bytes are changed,
 * etc. ("second pointer") Etc.
 * @param pointer gives the pointer value
 * @param location gives the location (in terms of 4 byte ints)
 * in the m_buffer to change
 */
void Block::SetPointer(int pointer, int location){
    int start = location * sizeof(int);
    int end = start + sizeof(int);
    int value = pointer;
    int *p;
    p = &value;
    unsigned char *s;
    s = (unsigned char*)p;
    //std::cout<<*p;
    int j = 0;
    for(int i = start; i < end; i++){
        m_buffer[i] = s[j];
        j++;
    }

    //delete p;
    //delete s;
}

/**
 * Retrieves one of the pointers in the m_buffer
 * @param location gives the relative 4 byte location to access.
 * See SetPointer.
 * @return the value of the pointer from m_buffer.
 */
int Block::GetPointer(int location){
    int start = location * sizeof(int);
    int end = start + sizeof(int);
    int value = 6;
    int *p;
    p = &value;
    unsigned char *s;
    s = (unsigned char*)p;//&value;
    int j = 0;
    for(int i = start; i < end; i++){
        s[j] = m_buffer[i];
        j++;
    }
    //delete p;
    //delete s;
    return value;
}

/**
 * Print out the contents of the Block. Prints the m_buffer
 * bytes in hex and decimal representation. Prints out the values
 * of the other data members. For debugging.
 */
void Block::Print(){

    if(m_blockNumber == 0){
        printf("Mother Block (Block %d).\n",m_blockNumber);
        printf("Freelist start Block Number: %d, end block number: %d, number"
                " of free blocks: %d\n",GetPointer(0),GetPointer(1),
                GetPointer(2));
    }
    else{
        printf("Block number: %d\n",m_blockNumber);
        printf("Next pointer: %d\n",GetPointer(0));
    }
    printf("Buffer: ");
    int i = 0;
    while(i!=4){
        printf("%c",m_buffer[i]);
        i++;
    }
    while(i!=Disk::DEFAULT_BLOCK_SIZE){
        printf("%c",m_buffer[i]);
        i++;
    }
    printf("\n");

}

void Block::Test() {
//        char *s = NULL;
//       char *blockName = "Block1";
//        int numberOfBlocks = 1000;
        int cmd = 0;
//        Disk *disk1 = NULL;
        Block test = Block(27, 512);
        
        

//        int blockNumber = 27;
//        int byteToWrite;

        for (;;) {
            unsigned char m_buffer[Disk::DEFAULT_BLOCK_SIZE] = "abcdesf";
            printf("\nmain: Enter command:\n");
            printf("   0) Exit program\n");
            printf("   1) getBlockNumber\n");
            printf("   2) setPointer \n");
            printf("   3) getPointer\n");
            printf("   4) setBuffer\n");
            printf("   5) getBuffer\n");
            printf("   6) clearBuffer\n");

//        unsigned char *block = new unsigned char[Disk::DEFAULT_BLOCK_SIZE];

            printf("Enter command: ");
            scanf("%d", &cmd);

            switch (cmd) {
                case 0:
                    exit(1);
                    break;

                case 1: // get the block number
                    test.GetBlockNumber();
                    printf("The block is %d\n",test.GetBlockNumber());
                    break;
                case 2: // set pointer
                    test.SetPointer(-1,0);
                    printf("The Pointer has been set!\n");
                    break;
                case 3: // get pointer
                    test.GetPointer(0);
                    printf("The Pointer is %d\n",test.GetPointer(0));
                    break;
                case 4: // set buffer
                    //m_buffer = (unsigned char*)"abcde";
                    unsigned char* nit = new unsigned char[Disk::DEFAULT_BLOCK_SIZE];
                    for(int t = 0; t<22;t++)
                        nit[t] = (unsigned char) t +65;
                    test.SetBuffer(nit);
                    nit = NULL;
                    //test.SetBuffer(m_buffer);
                    printf("The buffer has been set!\n");
                    break;
                case 5: // get buffer
                    test.GetBuffer();
                    printf("The Buffer is %s\n",test.GetBuffer());
                    break;
                case 6: // clear buffer
                    test.ClearBuffer();
                    printf("The buffer has been cleared!\n");
                    break;
                default:
                    printf("main: Bad command!\n");
                    break;
            } // end switch
        } // end for
    } // end Test()
